#pragma once
#ifndef _KH_CORE_TASKER_H
#define _KH_CORE_TASKER_H
#include <Kharlia/Core/Common.h>
#include <Kharlia/Core/Subsystem.h>

#undef Yield // From WinBase.h

namespace kharlia { namespace core {

void _InitTaskerBindings();
void _FiniTaskerBindings();

/// Describes a sleeping tasklet.
///
struct FSleepingTasklet {
    /// The channel the tasklet is blocked on
    handle<PyChannelObject> Channel;
    float BeginTime;
    /// Time in seconds when we're allowed to unblock.
    float EndTime;
    /// Whether or not to send delta time over channel
    bool SendDelta;
    uint8 _Unused[3];
    FSleepingTasklet(handle<PyChannelObject> ch,
                     float bt, float et, bool sendDelta):
        Channel(ch),
        BeginTime(bt),
        EndTime(et),
        SendDelta(sendDelta) {}
};

C_ASSERT(sizeof(FSleepingTasklet) == 16);

/// Options struct for a tasklet group.
///
struct FTaskletGroupOptions {
    uint32 Timeout;
    bool ThreadBlock;
    bool SoftSwitch;
    bool IgnoreNesting;
    bool TotalTimeout;
    // Extended options
    /// If true, tasklets found runnable before this group is supposed
    /// to run will be reinserted into the default tasker.
    bool InsertStraysIntoDefault;
    /// Channel preference to use when waking taskels in run
    sint8 RunPreference;

    FTaskletGroupOptions():
        Timeout(0),
        ThreadBlock(false),
        SoftSwitch(false),
        IgnoreNesting(false),
        TotalTimeout(false),
        InsertStraysIntoDefault(false),
        RunPreference(1) {}
};

struct FTaskletGroup {
    double LastRunTime;
    /// Yield channel.
    handle<PyChannelObject> YieldChannel;
    /// List of sleeping tasklets.
    std::list<FSleepingTasklet> Sleepers;
    /// List of pending tasklet inserts.
    std::list<handle<PyTaskletObject>> Inserts;
};

class KH_CORE_DECLSPEC CTasker: public CSubsystem {
    /// Number of tasklet groups. Always at least one.
    uint8 mGroupCount;
    /// The group used for any operations where a specific group is
    /// unspecified.
    uint8 mDefaultGroup;
    /// The current running group. NO_GROUPS otherwise.
    uint8 mCurrentGroup;
    double mLocalTime;
    /// Options for tasklet groups, indexed by group number.
    std::vector<FTaskletGroupOptions> mGroupOptions;
    std::vector<FTaskletGroup> mGroups;

    bool GetRealGroup(uint8& group) const;
    handle<> SleepImpl(float seconds, uint8 group, int sl);

    KH_DECL_GC()

    /// Pointer to default tasker.
    static shared_ptr<CTasker> msDefault;

public:
    /// Last valid group number
    static const uint8 LAST_GROUP = 199;
    /// Maximum number of groups allowed
    static const uint8 MAX_GROUP_COUNT = 200;
    /// Inserts into next group are run ASAP
    static const uint8 NEXT_GROUP = 249;
    /// Special group number for representing all groups
    static const uint8 ALL_GROUPS = 250;
    /// Special group number for no or unspecified group
    static const uint8 NO_GROUPS = 251;
    /// Special group number representing current running group
    static const uint8 CURRENT_GROUP = 252;
    /// Special group number representing default group
    static const uint8 DEFAULT_GROUP = 253;
    static const uint8 UNSPECIFIED_GROUP = 255;

    CTasker(PyObject* self, uint8 groupCount, const FTaskletGroupOptions& defaultOptions);
    virtual ~CTasker();

    /// Default tick implementation, calls Tick(deltaTime, ALL_GROUPS)
    virtual void Tick(float deltaTime);
    /// Tick implementation. Can be used with specific group number, 
    /// ALL_GROUPS, or NO_GROUPS. NO_GROUPS will only update time.
    virtual void Tick(float deltaTime, uint8 group);
    /// Run a tasklet group.
    void Run(uint8 group);
    void Init();
    void Exit();
    /// Compile a list of scheduled, yielded, sleeping, and insert-pending
    /// tasklets in the specified group. ALL_GROUPS and NO_GROUPS are valid.
    /// If NO_GROUPS, compiles list of currently scheduled tasklets.
    /// Returns list object or None.
    object GetTaskletList(uint8 group) const;
    /// Kills all tasklets in specified group or ALL_GROUPS. If killCurrent
    /// is true and the current tasklet is in the specified group, a
    /// TaskletExit exception will be thrown.
    void KillTasklets(uint8 group, bool killCurrent=false);

    /// Tasklet creation
    object CreateTasklet(object callable=object());
    /// Inserts tasklet into the specified group.
    /// If group is currently running, returns with no changes. Otherwise
    /// removes tasklet from scheduler and adds to pending insert list
    /// for group.
    bool InsertTasklet(object tasklet, uint8 group=0);
    /// Insert all scheduled runnable tasklets into the specified group
    int InsertRunnables(uint8 group=0);
    /// Put the current tasklet to sleep. Returns time since sleep call.
    float Sleep(float seconds, uint8 group=UNSPECIFIED_GROUP);
    /// Stackless implementation of sleep method that allows soft switching
    static PyObject* SleepPy(PyObject* self, PyObject* args, PyObject* kwds);
    /// Yield execution until the next run. Returns time since yield call.
    float Yield(uint8 group=UNSPECIFIED_GROUP);
    /// Stackless implementation of yield method that allows soft switching
    static PyObject* YieldPy(PyObject* self, PyObject* args, PyObject* kwds);

    KH_DEF_GET(uint8, GroupCount)
    KH_DEF_GET(uint8, DefaultGroup)
    KH_DEF_GET(uint8, CurrentGroup)
    void SetDefaultGroup(uint8 num);

    /// Get reference to options for tasklet group.
    FTaskletGroupOptions& GetGroupOptions(uint8 group);

    /// Sends value on channel repeatedly until there are no more listeners.
    static int ClearChannel(object channel, object value=object());
    static shared_ptr<CTasker> GetDefault();
    static CTasker* GetDefaultPtr();
    /// Set the default tasklet manager and group for stray tasklets
    static void SetDefault(shared_ptr<CTasker> tasker);
    /// Insert tasklet into the default tasklet group
    static bool InsertTaskletDefault(object tasklet);
    /// Insert all scheduled, runnable tasklets into the default tasklet group
    static int InsertRunnablesDefault();

    static PyObject* SleepDefPy(PyObject* self, PyObject* args, PyObject* kwds);
    static PyObject* YieldDefPy(PyObject* self, PyObject* args, PyObject* kwds);


    static object GetTaskletExtClass();

    friend void _InitTaskerBindings();
    friend void _FiniTaskerBindings();
};

inline void CTasker::Tick(float deltaTime) {
    Tick(deltaTime, ALL_GROUPS);
}

inline void CTasker::Init() {
    call_method<void>(mSelf, "Init");
}

inline void CTasker::Exit() {
    call_method<void>(mSelf, "Exit");
}

inline FTaskletGroupOptions& CTasker::GetGroupOptions(uint8 group) {
    return mGroupOptions.at(group);
}

inline shared_ptr<CTasker> CTasker::GetDefault() {
    return msDefault;
}

inline CTasker* CTasker::GetDefaultPtr() {
    return msDefault.get();
}

}} // namespace kharlia::core

KH_HAS_BACK_REFERENCE(kharlia::core::CTasker)

#endif